<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=yes">
    <title>Title</title>
    <script src="/static/jquery.min.js"></script>
    <style type="text/css">
    	div {margin:8px; margin-left: 10px; padding: 3px; border-radius: 5px; width: 80%;}
    	#test {background-color: #eee;}
    	.cc {background-color: #ccc;}
    	.dd {background-color: #aaa;}
    	.d3 {background-color: #888;}
    </style>
</head>
<body>
	<div id="target"></div>

<!-- 注意{mm}是内外层都有 -->
<template id="tpl">
	<div id="test">
		<div>{aa}</div>
		<div>{bb}</div>
		<div>{mm}</div>
		<div>
			<div class="cc">{n} : {v} : {mm}</div>
		</div>
		<div class="dd">
			<div>{d1}</div>
			<div>{d2}</div>
			<div>{mm}</div>
			<div class="d3">{x} : {y} : {mm}</div>
		</div>
	</div>
</template>
   
    <script type="text/javascript">
        let arr = [{"aa":"aa","bb":"bb","mm":"1","cc":[{"n":"n1","v":"v1","mm":"2"},{"n":"n2","v":"v2","mm":"2"},{"n":"n3","v":"v3","mm":"2"}],"dd":{"d1":"d1","d2":"d2","d3":[{"x":"x1","y":"y1","mm":"4"},{"x":"x2","y":"y2","mm":"4"},{"x":"x3","y":"y3","mm":"4"}],"mm":"3"}}];

        let tpl = document.getElementById('tpl').innerHTML;
        let node = htmlToNode(tpl);
        //console.log(node);

        document.getElementById('target').innerHTML = repeatNode(node, arr);

        /**
        * @param node HTML DOM节点, 注意不是string
        * @param arr json数组 注意是数组类型
        * @return string 返回HTML字符串, 注意不是DOM节点
        */
        function repeatNode(node, arr) {

        	let out = [];
	        for (let i=0; i<arr.length; i++) {
	            let tmp = node.outerHTML;
	            console.log(tmp);
	            tmp = tmp.replace(/\s/g, ' '); //去掉回车换行, 减少空白符

	            let map = arr[i];
	            console.log(map);

	            //先渲染内层的数组
	            for (let j in map) {
	            	if (map[j] instanceof Array) { //数组, 递归替换
	            		let subNode = node.querySelector('.'+j);
	            		let subHtml = repeatNode(subNode, map[j]); //递归
	            		let subTpl = subNode.outerHTML.replace(/\s/g, ' ');
	            		tmp = tmp.replace(subTpl, subHtml);

	            	}
	            }

	            //再渲染内层的对象
	            for (let j in map) {

	            	if (map[j] instanceof Object && !(map[j] instanceof Array)) { //对象, 递归替换
	            		
	            		let subNode = node.querySelector('.'+j);
	            		let subHtml = repeatNode(subNode, [map[j]]); //递归
	            		let subTpl = subNode.outerHTML.replace(/\s/g, ' ')
	            		tmp = tmp.replace(subTpl, subHtml);

	            	}
	            }

	            //最后渲染外层的键值对/字符串
	            for (let j in map) {
	            	if (typeof map[j] === 'string') { //字符串, 直接替换
	            		let re = new RegExp('{' + j + '}', 'g');
	                	tmp = tmp.replace(re, map[j]);

	            	}
	            }

	            out.push(tmp);
	        }

	        return out.join('');
        }

        function htmlToNode(html) {
        	let div = document.createElement('div');
	        let pos = html.indexOf('<'); //避免开头有空白
	        div.innerHTML = html.substring(pos);
	        return div.firstChild;
        }

               
    </script>

</body>
</html>